use std::convert::TryInto;

use serde::Deserialize;
use serde::Serialize;
use yata::core::{ Error, PeriodType, ValueType, Window };
use yata::core::MovingAverage;
use yata::core::Method;
use yata::helpers::{ Buffered, Peekable };

/// Ratio of Current Volume compare to Previous Period Moving-Average-of-Volume
///
/// # Parameters
///
/// Has a single parameter `length`: `PeriodType`
///
/// `length` should be > `0`
///
/// # Input type
///
/// Input type is `ValueType`
///
/// # Output type
///
/// Output type is `ValueType`
///
/// # Examples
///
/// ```
/// use yata::prelude::*;
/// use model::malines::smpr::SMP;
///
///    // SMP of length=3
///    let mut smpr = SMP::new(3, &100.0).unwrap();
///
///    smpr.next(&50.0);
///    smpr.next(&50.0);
///    smpr.next(&50.0);
///
///    let value = smpr.peek();
///    assert_eq_float(value, 1.0);
///
///    smpr.next(&200.0);
///    let value = smpr.peek();
///    assert_eq_float(value, 4.0);

/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SMPR {
    divider: ValueType,
    avg_volume: ValueType,
    ratio: ValueType,
    window: Window<ValueType>,
}

impl SMPR {
    /// Returns inner `Window`. Useful for implementing in other methods and indicators.
    #[inline]
    #[must_use]
    pub const fn get_window(&self) -> &Window<ValueType> {
        &self.window
    }

    /// Returns 1/`length`. Useful for implementing in other methods and indicators.
    #[inline]
    #[must_use]
    pub const fn get_divider(&self) -> ValueType {
        self.divider
    }
}

impl Method for SMPR {
    type Params = PeriodType;
    type Input = ValueType;
    type Output = Self::Input;

    fn new(length: Self::Params, &volume: &Self::Input) -> Result<Self, Error> {
        match length {
            0 => Err(Error::WrongMethodParameters),
            length =>
                Ok(Self {
                    divider: (length as ValueType).recip(),
                    avg_volume: volume,
                    ratio: 1.0,
                    window: Window::new(length, volume),
                }),
        }
    }

    #[inline]
    fn next(&mut self, &volume: &Self::Input) -> Self::Output {
        self.ratio = volume / self.avg_volume;

        let prev_volume = self.window.push(volume);

        // Update the average volume
        self.avg_volume += (volume - prev_volume) * self.divider;
        return self.ratio;
    }
}

impl MovingAverage for SMPR {}

impl Buffered<ValueType> for SMPR {
    fn get(&self, index: usize) -> Option<ValueType> {
        let index = index.try_into().ok()?;
        self.window.get(index).copied()
    }
}

impl Peekable<<Self as Method>::Output> for SMPR {
    fn peek(&self) -> <Self as Method>::Output {
        self.ratio
    }
}

#[cfg(test)]
mod tests {
    use yata::core::Method;
    use super::SMPR;
    use yata::helpers::assert_eq_float;
    use yata::prelude::Peekable;

    #[test]
    fn test_smpr_const() {
        // SMP of length=3
        let mut smpr = SMPR::new(3, &100.0).unwrap();

        smpr.next(&50.0);
        smpr.next(&50.0);
        smpr.next(&50.0);
        println!("{:?}", smpr);
        let value = smpr.peek();
        assert_eq_float(value, 1.0);

        println!("------");
        smpr.next(&200.0);
        println!("{:?}", smpr);
        let value = smpr.peek();
        assert_eq_float(value, 4.0);
    }
}
